package com.example.service.impl;

import com.alibaba.csp.sentinel.util.AssertUtil;
import com.example.aop.AopLock;
import com.example.dao.SeckillMapper;
import com.example.dao.StudentMapper;
import com.example.dao.SuccessKilledMapper;
import com.example.enums.SeckillStatEnum;
import com.example.model.Result;
import com.example.model.SuccessKilled;
import com.example.service.SecKillService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Date;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Service("secKillService")
public class SecKillServiceImpl implements SecKillService {

    private final static Logger LOGGER = LoggerFactory.getLogger(SecKillServiceImpl.class);
    private static int corePoolSize = Runtime.getRuntime().availableProcessors();

    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize,
            corePoolSize + 5,
            10l,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(1000)
    );
    SeckillMapper seckillMapper;
    @Resource
    public void setStudentMapper(SeckillMapper seckillMapper) {
        this.seckillMapper = seckillMapper;
    }

    SuccessKilledMapper successKilledMapper;
    @Resource
    public void setStudentMapper(SuccessKilledMapper successKilledMapper) {
        this.successKilledMapper = successKilledMapper;
    }
    @Override
    public int deleteById(Long seckillId) {
        return successKilledMapper.deleteById(seckillId);
    }

    @Override
    public void deleteRecord() {
        successKilledMapper.deleteRecord();
    }


    @Override
    public Result buyShopping(Long seckillId, Integer buyUserNum) {
        // 删除原来的抢购记录
        this.deleteById(seckillId);
        CountDownLatch countDownLatch = new CountDownLatch(buyUserNum);
        LOGGER.error("开始购买");

        for (int i = 0; i < buyUserNum; i++) {
//           Runnable task = new Runnable() {
//               @Override
//               public void run() {
//
//               }
//           };
            Runnable task = () -> {
                Random random = new Random();
                Long userId = random.nextLong();
                Result result = this.startSeckill(seckillId, userId);
                // 抢购失败
                if (result != null) {
                    LOGGER.info("用户:{}{}{}", userId, result.get("msg"), "成功抢购商品");
                } else {
                    LOGGER.error("用户:{}{}", userId, "哎呦喂，人也太多了，请稍后！");

                }
                countDownLatch.countDown();
            };
            threadPoolExecutor.execute(task);
        }
        try {
            countDownLatch.await();
            // 查询出出售记录
            Long seckillCount = successKilledMapper.getCountById(seckillId);
            LOGGER.info("一共秒杀出{}件商品", seckillCount);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return Result.ok();
    }

    /**
     * 这里添加了@AopLock的注解  实现Aop + 锁
     * @param seckillId
     * @param userId
     * @return
     */
    @Override
    @AopLock
    public Result buyShoppingByAOP(Long seckillId, Long userId) {
        return this.startSeckill(seckillId, userId);
    }

    @Override
    @Transactional
    public Result startSeckill(Long secKillId, Long userId) {
        // 查看库存
        Long num = seckillMapper.getNumBySeckillId(secKillId);
        if (num > 0) {
            seckillMapper.updateNumBySecKillId(secKillId);
            SuccessKilled successKilled = new SuccessKilled();
            successKilled.setSeckillId(secKillId);
            successKilled.setCreateTime(new Date());
            successKilled.setState(0);
            successKilled.setUserId(userId);
            successKilledMapper.insertSelective(successKilled);
            return Result.ok(SeckillStatEnum.SUCCESS);
        }
        return Result.error(SeckillStatEnum.END);
    }
    /**
     * 两种写法
     * 第一种在查看库存上for update，设置排他锁，这样在事务结束之前，其他事务都无法进行读写或者更新操作。
     *  select number from seckill where seckill_id = #{seckillId,jdbcType=BIGINT} for update
     *
     *
     * 第二种，update的时候锁上表
     * 这里演示第二种
     *
     *
     */
    @Override
    public Result buyShoppingForUpdate(long seckillId, Long userId) {

        int num = seckillMapper.updateNumByIdAndNum(seckillId);
        if (num > 0) {
            SuccessKilled successKilled = new SuccessKilled();
            successKilled.setSeckillId(seckillId);
            successKilled.setCreateTime(new Date());
            successKilled.setState(0);
            successKilled.setUserId(userId);
            successKilledMapper.insertSelective(successKilled);
            return Result.ok(SeckillStatEnum.SUCCESS);
        }
        return Result.error(SeckillStatEnum.END);
    }

    /**
     * 乐观锁秒杀接口
     * 这里会出现少买的情况，比如60个人同时抢购，实际上只有10个人抢到，另外50个人未抢到。 原因就是version没对上
     * 乐观锁核心实现字段 version
     *  1.  先查看 当前version号
     *  2.  update的时候，将当前version号作为条件，并且将当前version +1 来实现乐观锁
     *   update table set version = version + 1  where version = ?
     * @param seckillId
     * @param userId
     * @return
     */
    @Override
    public Result buyShoppingForHapiness(long seckillId, Long userId) {
        // 查看当前数据库版本号
        Long versionId = seckillMapper.selectVersionIdBySecKillId(seckillId);

        int num = seckillMapper.updateByVersion(seckillId, versionId);
        if (num > 0) {
            SuccessKilled successKilled = new SuccessKilled();
            successKilled.setSeckillId(seckillId);
            successKilled.setCreateTime(new Date());
            successKilled.setState(0);
            successKilled.setUserId(userId);
            successKilledMapper.insertSelective(successKilled);
            return Result.ok(SeckillStatEnum.SUCCESS);
        }
        return Result.error(SeckillStatEnum.END);
    }

}
